package com.damai.aop.impl;

import java.lang.reflect.Method;

import com.citywithincity.api.ApiConfig;
import com.citywithincity.utils.ClassUtil;
import com.damai.aop.MethodAop;
import com.damai.aop.MethodCaller;
import com.damai.aop.MethodInterceptor;
import com.damai.lang.Lang;
import com.damai.utils.CtClassBuilder;
import com.damai.utils.CtMethodBuilder;
import com.damai.utils.LogUtil;

import javassist.CannotCompileException;
import javassist.Modifier;
import javassist.NotFoundException;

public class AopBuilder {

	@SuppressWarnings("unused")
	public static MethodCaller makeCaller(Class<?> clazz, Object target, Method method)
			throws InstantiationException, IllegalAccessException, CannotCompileException, NotFoundException {

		return new DefaultMethodCaller(method);

	}

	private static boolean contains(Method[] methods) {
		for (Method method : methods) {
			if (method.isAnnotationPresent(MethodAop.class)) {
				return true;
			}
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	public static <T> T proxy(T obj) throws Exception {
		Class<?> clazz = obj.getClass();
		Method[] methods = ClassUtil.getPublicMethods(clazz);
		if (!contains(methods)) {
			return obj;
		}

		MethodInfo[] infos = new MethodInfo[methods.length];
		CtClassBuilder builder = new CtClassBuilder(clazz.getName() + "AopProxy");
		builder.setSuperClass(clazz);
		
		builder.addArrayField(MethodInfo.class, "infos",Modifier.PUBLIC | Modifier.STATIC);
		int methodIndex = 0;
		for (Method method : methods) {
			if (method.isAnnotationPresent(MethodAop.class)) {
				MethodAop aop = method.getAnnotation(MethodAop.class);
				Class<? extends MethodInterceptor>[] values = aop.value();
				MethodInterceptor[] methodInterceptors = new MethodInterceptor[values.length];
				int index = 0;
				for (Class<? extends MethodInterceptor> interceptorClass : values) {
					try {
						methodInterceptors[index++] = interceptorClass.newInstance();
					} catch (Exception e) {
						throw new RuntimeException("Init " + interceptorClass + " error!", e);
					}
				}

				MethodInfo info = new MethodInfo();
				info.interceptors = methodInterceptors;
				info.method = method;
				info.caller = makeCaller(clazz, obj, method);
				infos[methodIndex] = info;
				CtMethodBuilder methodBuilder = builder.beginMethod(method);
				StringBuilder sb = new StringBuilder();
				sb.append("com.damai.aop.MethodInvoker methodInvoker = new com.damai.aop.impl.MethodInvokerImpl(infos["
						+ methodIndex + "], $0.model, new Object[]{");
				Class<?>[] types = method.getParameterTypes();
				boolean first = true;
				for (int i = 0; i < types.length; ++i) {
					if (first) {
						first = false;
					} else {
						sb.append(",");
					}
					sb.append("$" + (i + 1));
				}
				// $1,$2
				sb.append("});methodInvoker.invoke();");
				
				String returnType = method.getReturnType().getSimpleName();
				
				if(returnType.equalsIgnoreCase("void")){
				
				}else{
					//sb.append("return null;");
					sb.append("return ").append("(").append(method.getReturnType().getName())
					.append(")").append("methodInvoker.getReturnValue();");
				}
				
				if (ApiConfig.debug()) {

					LogUtil.log("%s", sb.toString());

				}
				methodBuilder.setBody(sb.toString());

				builder.endMethod();
			} 

			++methodIndex;
		}

		builder.addContructor(Lang.toArray(clazz, MethodInfo[].class), "{$0.model=$1;$0.infos=$2;}");

		builder.setSuperClass(clazz);
		return builder.make().newInstance();

	}
}
